#include "CGIVulkan.h"
#include "Runtime/Log/Logger.h"
#include "CGIVulkanResource.h"
#include "../CGICommandList.h"
#ifdef _WIN32
PFN_vkCreateDebugReportCallbackEXT __vkCreateDebugReportCallback = nullptr;
PFN_vkDestroyDebugReportCallbackEXT __vkDestroyDebugReportCallback = nullptr;
PFN_vkCreateWin32SurfaceKHR __vkCreateWin32Surface = nullptr;
VkDebugReportCallbackEXT sVulkanDebugReportCallback = nullptr;
#endif
Alice::DynamicCGI* PlatformCreateDynamicCGI()
{
    return new Alice::CGIVulkan;
}
namespace Alice{
    static int sEnablableLayerCount=0;
    static char** sEnabledLayers=nullptr;
    static const char* sEnabledExtensions[] = {
        VK_KHR_SURFACE_EXTENSION_NAME ,
        VK_KHR_WIN32_SURFACE_EXTENSION_NAME,
        VK_EXT_DEBUG_REPORT_EXTENSION_NAME
    };
    static void InitPreferedLayers() {
        uint32_t layerCount;
        vkEnumerateInstanceLayerProperties(&layerCount, nullptr);
        if (layerCount == 0) {
            return;
        }
        VkLayerProperties* layers = new VkLayerProperties[layerCount];
        vkEnumerateInstanceLayerProperties(&layerCount, layers);
        int enabledLayerCount = 0;
        for (uint32_t i = 0; i < layerCount; ++i) {
            if (nullptr!=strstr(layers[i].layerName, "validation")) {
                enabledLayerCount++;
            }
        }
        sEnablableLayerCount = enabledLayerCount;
        if (enabledLayerCount > 0) {
            sEnabledLayers = new char* [enabledLayerCount];
            int enabledLayerIndex = 0;
            for (uint32_t i = 0; i < layerCount; i++) {
                if (nullptr != strstr(layers[i].layerName, "validation")) {
                    sEnabledLayers[enabledLayerIndex] = new char[64];
                    memset(sEnabledLayers[enabledLayerIndex], 0, 64);
                    strcpy_s(sEnabledLayers[enabledLayerIndex], 64, layers[i].layerName);
                    printf("[%d/%d] InitPreferedLayers enable %s\n",enabledLayerIndex,enabledLayerCount,layers[i].layerName);
                    enabledLayerIndex++;
                }
            }
        }
        delete[] layers;
    }
    static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(
        VkDebugReportFlagsEXT                       flags,
        VkDebugReportObjectTypeEXT                  objectType,
        uint64_t                                    object,
        size_t                                      location,
        int32_t                                     messageCode,
        const char* pLayerPrefix,
        const char* pMessage,
        void* pUserData) {
        Debug("debug report : %s\n", pMessage);
        return VK_FALSE;
    }
    static bool InitDebugger(VkInstance inVkInstance) {
        VkDebugReportCallbackCreateInfoEXT debugReportCallbackCreateInfo = {};
        debugReportCallbackCreateInfo.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT;
        debugReportCallbackCreateInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT;
        debugReportCallbackCreateInfo.pfnCallback = debugCallback;
        if (VK_SUCCESS != __vkCreateDebugReportCallback(inVkInstance,
            &debugReportCallbackCreateInfo, nullptr,
            &sVulkanDebugReportCallback)) {
            return false;
        }
        return true;
    }
    static VkPhysicalDevice SelectPhysicalDevice(VkInstance inVkInstance) {
        uint32_t deviceCount = 0;
        vkEnumeratePhysicalDevices(inVkInstance, &deviceCount, nullptr);
        struct PhysicalDeviceInfo
        {
            PhysicalDeviceInfo(uint32 InOriginalIndex, VkPhysicalDevice InPhysicalDevice)
                : mOriginalIndex(InOriginalIndex)
                , mPhysicalDevice(InPhysicalDevice)
            {
                mPhysicalDeviceProperties2 = {};
                mPhysicalDeviceProperties2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
                mPhysicalDeviceIDProperties = {};
                mPhysicalDeviceIDProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ID_PROPERTIES;
                mPhysicalDeviceProperties2.pNext = &mPhysicalDeviceIDProperties;
                vkGetPhysicalDeviceProperties2(mPhysicalDevice, &mPhysicalDeviceProperties2);
            }
            uint32 mOriginalIndex;
            VkPhysicalDevice mPhysicalDevice;
            VkPhysicalDeviceProperties2 mPhysicalDeviceProperties2;
            VkPhysicalDeviceIDProperties mPhysicalDeviceIDProperties;
        };
        std::vector<PhysicalDeviceInfo*> deviceInfos;
        deviceInfos.reserve(deviceCount);
        VkPhysicalDevice* allVulkanGPU = new VkPhysicalDevice[deviceCount];
        vkEnumeratePhysicalDevices(inVkInstance, &deviceCount, allVulkanGPU);
        for (int i = 0; i < deviceCount; i++) {
            deviceInfos.push_back(new PhysicalDeviceInfo(i, allVulkanGPU[i]));
        }
        std::sort(deviceInfos.begin(), deviceInfos.end(), [](PhysicalDeviceInfo*a,PhysicalDeviceInfo*b){
            if (a->mPhysicalDeviceProperties2.properties.deviceType == b->mPhysicalDeviceProperties2.properties.deviceType)
            {
                return a->mOriginalIndex < b->mOriginalIndex;
            }
            return (a->mPhysicalDeviceProperties2.properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) ||
                (b->mPhysicalDeviceProperties2.properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_CPU);
        });
        VkPhysicalDevice gpu = deviceInfos[0]->mPhysicalDevice;
        delete[] allVulkanGPU;
        return gpu;
    }
    CGIVulkan::CGIVulkan() {
        gDynamicCGI=this;
        mAPIVersion=VK_API_VERSION_1_1;
        VkApplicationInfo appInfo = {};
        appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
        appInfo.pApplicationName = "RealEngineEdu";
        appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
        appInfo.pEngineName = "RealEngine";
        appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
        appInfo.apiVersion = mAPIVersion;
        InitPreferedLayers();
        VkInstanceCreateInfo createInfo = {};
        createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
        createInfo.pApplicationInfo = &appInfo;
        createInfo.enabledExtensionCount = _countof(sEnabledExtensions);
        createInfo.ppEnabledExtensionNames = sEnabledExtensions;
        createInfo.enabledLayerCount = sEnablableLayerCount;
        createInfo.ppEnabledLayerNames = sEnabledLayers;
        if (vkCreateInstance(&createInfo, nullptr, &mVkInstance) != VK_SUCCESS) {
            Debug("create vulkan instance fail");
        }else{
            Debug("CGIVulkan Init Success!");
            __vkCreateDebugReportCallback = (PFN_vkCreateDebugReportCallbackEXT)vkGetInstanceProcAddr(mVkInstance,
                "vkCreateDebugReportCallbackEXT");
            __vkDestroyDebugReportCallback = (PFN_vkDestroyDebugReportCallbackEXT)vkGetInstanceProcAddr(mVkInstance,
                "vkDestroyDebugReportCallbackEXT");
            __vkCreateWin32Surface = (PFN_vkCreateWin32SurfaceKHR)vkGetInstanceProcAddr(mVkInstance,
                "vkCreateWin32SurfaceKHR");
            InitDebugger(mVkInstance);
            VkPhysicalDevice physicDevice = SelectPhysicalDevice(mVkInstance);
            mDevice = new CGIVulkanDevice(physicDevice,mAPIVersion);
            Debug("API Version %d.%d.%d", VK_VERSION_MAJOR(mDevice->mVkPhysicalDeviceProperties.apiVersion), 
                VK_VERSION_MINOR(mDevice->mVkPhysicalDeviceProperties.apiVersion), 
                VK_VERSION_PATCH(mDevice->mVkPhysicalDeviceProperties.apiVersion));
            mDevice->InitGPU();
        }
    }
    bool CGIVulkan::Init(){
        return true;
    }
    SmartPtr<CGITexture> CGIVulkan::CGICreateTexture2DFromResource(EPixelFormat Format, uint32 SizeX, uint32 SizeY, uint32 NumMips, uint32 NumSamples, VkImage Resource,
        ETextureCreateFlags Flags, const FClearValueBinding& ClearValueBinding, const CGIVulkanRHIExternalImageDeleteCallbackInfo& ExternalImageDeleteCallbackInfo)
    {
        const CGITextureCreateDesc Desc = CGITextureCreateDesc::Create2D(TEXT("VulkanTexture2DFromResource"), SizeX, SizeY, Format)
            .SetClearValue(ClearValueBinding)
            .SetFlags(Flags)
            .SetNumMips(NumMips)
            .SetNumSamples(NumSamples)
            .DetermineInititialState();

        return new CGIVulkanTexture(*mDevice, Desc, Resource, ExternalImageDeleteCallbackInfo);
    }
    SmartPtr<CGITexture> CGIVulkan::CGIGetViewportBackBuffer(CGIViewport* inViewport)
    {
        CGICommandListImmediate& RHICmdList = CGICommandListImmediate::Get();

        CGIVulkanViewport* Viewport = (CGIVulkanViewport*)(inViewport);

        //if (Viewport->mSwapChain)
        //{
        //    Viewport->mSwapChain->RenderThreadPacing();
        //}

        return Viewport->GetBackBuffer(RHICmdList);
    }

}